package air.util;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;

/**
 * 对称加密、非对称加密工具库
 * 
 * @author zhangyangtao
 */
public class SecurityUtil {
    
    public static final String CHARSET = "UTF-8";
    
    /** 对称加密具体算法实现 */
    public static final String AES_ALGORITHM = "AES/CFB/PKCS5Padding";
    
    /** 非对称加密具体算法实现 */
    public static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
    
    /**
     * 签名算法的实现，先根据SHA256生成摘要，再对摘要做RSA加密
     * 
     * 注意: 其内部有其他处理，与手动计算SHA256再手动加密生成的结果可能不一样
     */
    public static final String SIGN_SHA256_RSA_ALGORITHM = "SHA256withRSA";
    
    /**
     * 获取随机生成的128位AES加密密钥
     * 
     * @return AES加密所用的128位密钥
     * 
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidKeyException
     * @throws NoSuchPaddingException
     */
    public static byte[] getRandomAES128SecretKey() throws NoSuchAlgorithmException, UnsupportedEncodingException,
            IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchPaddingException {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128);
        SecretKey secretKey = keyGen.generateKey();
        return secretKey.getEncoded();
    }
    
    /**
     * 获取随机生成的128位AES加密初始向量iv
     * 
     * @return AES加密所用的128位初始向量iv
     */
    public static byte[] getRandomAES16IV() {
        SecureRandom r = new SecureRandom();
        byte[] iv = new byte[16];
        r.nextBytes(iv);
        return iv;
    }
    
    /**
     * 通过密钥对称加密数据
     * 
     * @param secretKey
     *            对称加密密钥
     * @param plaintext
     *            待加密明文
     * 
     * @return 对称加密后的密文
     * 
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     */
    public static String encryptAES(byte[] secretKey, String plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException, UnsupportedEncodingException {
        SecretKeySpec keySpec = new SecretKeySpec(secretKey, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        return Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes(CHARSET)));
    }
    
    /**
     * 通过密钥和初始向量iv加密数据
     * 
     * @param secretKey
     *            对称加密密钥
     * @param iv
     *            对称加密初始向量
     * @param plaintext
     *            待加密明文
     * 
     * @return 对称加密后的密文
     * 
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidAlgorithmParameterException
     */
    public static String encryptAES(byte[] secretKey, byte[] iv, String plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
        SecretKeySpec keySpec = new SecretKeySpec(secretKey, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        return Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes(CHARSET)));
    }
    
    /**
     * 通过密钥对称解密数据
     * 
     * @param secretKey
     *            对称加密密钥
     * @param ciphertext
     *            待解密的密文
     * 
     * @return 解密出的明文
     * 
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     */
    public static String decryptAES(byte[] secretKey, String ciphertext)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException, UnsupportedEncodingException {
        SecretKeySpec keySpec = new SecretKeySpec(secretKey, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        return new String(cipher.doFinal(Base64.decodeBase64(ciphertext)), CHARSET);
    }
    
    /**
     * 通过密钥和初始向量对称解密数据
     * 
     * @param secretKey
     *            对称加密密钥
     * @param iv
     *            对称加密初始向量
     * @param ciphertext
     *            对称加密的密文
     * 
     * @return 解密出的明文
     * 
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     * @throws InvalidAlgorithmParameterException
     */
    public static String decryptAES(byte[] secretKey, byte[] iv, String ciphertext)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
        SecretKeySpec keySpec = new SecretKeySpec(secretKey, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return new String(cipher.doFinal(Base64.decodeBase64(ciphertext)), CHARSET);
    }
    
    /**
     * 用公钥或私钥非对称加密数据
     * 
     * @param key
     *            公钥或私钥
     * @param plaintext
     *            待加密明文
     * 
     * @return 加密后的密文
     * 
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws UnsupportedEncodingException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidAlgorithmParameterException
     */
    public static String encryptRSA(Key key, String plaintext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException,
            BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes(CHARSET)));
    }
    
    /**
     * 用公钥或私钥非对称解密数据
     * 
     * @param key
     *            仅钥或私钥
     * @param ciphertext
     *            待解密的密文
     * 
     * @return 解密出的明文
     * 
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws DecoderException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws UnsupportedEncodingException
     * @throws InvalidAlgorithmParameterException
     */
    public static String decryptRSA(Key key, String ciphertext) throws NoSuchPaddingException, NoSuchAlgorithmException,
            InvalidKeyException, DecoderException, BadPaddingException, IllegalBlockSizeException,
            UnsupportedEncodingException, InvalidAlgorithmParameterException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        return new String(cipher.doFinal(Base64.decodeBase64(ciphertext)), CHARSET);
    }
    
    /**
     * 根据私钥字节串生成私钥对象
     * 
     * @param privateKeyBytes
     *            私钥字节串
     * 
     * @return 私钥对象
     * 
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PrivateKey getRSAPrivateKey(byte[] privateKeyBytes)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        return privateKey;
    }
    
    /**
     * 根据公钥字节串生成公钥对象
     * 
     * @param publicKeyBytes
     *            公钥字节串
     * 
     * @return 公钥对象
     * 
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PublicKey getRSAPublicKey(byte[] publicKeyBytes)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes);
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        return publicKey;
    }
    
    /**
     * 对入参数据以SHA256WithRSA方式生成签名，需要配合verifySignWithSHA256RSA()使用 注意:
     * 其内部有其他处理，与手动计算SHA256再手动加密生成的结果可能不一样
     * 
     * @param privateKey
     *            非对称加密私钥
     * @param content
     *            生成签名所用的字符串
     * 
     * @return 签名
     * 
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws DecoderException
     * @throws SignatureException
     * @throws UnsupportedEncodingException
     */
    public static String genSignWithSHA256RSA(PrivateKey privateKey, String content)
            throws NoSuchAlgorithmException, InvalidKeyException,
            DecoderException, SignatureException, UnsupportedEncodingException {
        Signature signature = Signature.getInstance(SIGN_SHA256_RSA_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(content.getBytes(CHARSET));
        return Base64.encodeBase64String(signature.sign());
    }
    
    /**
     * 根据入参验证签名是否正确，需要配合genSignWithSHA256RSA()使用 注意:
     * 其内部有其他处理，入参的cipherSign与手动计算SHA256再手动加密生成的结果可能不一样
     * 
     * @param publicKey
     *            非对称加密公钥
     * @param content
     *            生成签名所用的字符串
     * @param cipherSign
     *            签名
     * 
     * @return 签名是否合法
     * 
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws DecoderException
     * @throws SignatureException
     * @throws UnsupportedEncodingException
     */
    public static boolean verifySignWithSHA256RSA(PublicKey publicKey, String content, String cipherSign)
            throws NoSuchAlgorithmException,
            InvalidKeyException, DecoderException, SignatureException, UnsupportedEncodingException {
        Signature signature = Signature.getInstance(SIGN_SHA256_RSA_ALGORITHM);
        signature.initVerify(publicKey);
        signature.update(content.getBytes(CHARSET));
        return signature.verify(Base64.decodeBase64(cipherSign));
    }
    
    /**
     * 对入参map按key做升序排序后，以key=value&key=value形式连接在一起
     * 
     * @param paramMap
     * 
     * @return 根据入参排序连接后的字符串
     */
    public static String map2strAsc(Map<String, String> paramMap) {
        TreeMap<String, String> pairs = new TreeMap<String, String>(paramMap);
        StringBuilder sb = new StringBuilder();
        for (Entry<String, String> entry : pairs.entrySet()) {
            if (sb.length() != 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return sb.toString();
    }
    
    /**
     * 对入参map按key做升序排序后，以key=value&key=value形式连接在一起，计算sha256值
     * 
     * @param paramMap
     * 
     * @return 根据入参排序连接后的字符串，生成SHA256的结果
     */
    public static String sha256(Map<String, String> paramMap) {
        return DigestUtils.sha256Hex(map2strAsc(paramMap));
    }
    
    /**
     * 生成非对称加密的公钥和私钥的演示
     * 
     * @throws NoSuchAlgorithmException
     */
    public static void createKeyPairs() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        System.out.println(Base64.encodeBase64String(publicKey.getEncoded()));
        System.out.println(Base64.encodeBase64String(privateKey.getEncoded()));
    }
    
    public static void main(String[] args) {
        
        String input = "english,中文，한국어，عربية‎ لغة";
        System.out.println("原始明文: " + input);
        
        try {
            // 1. 测试对称加密
            byte[] secretKey = getRandomAES128SecretKey();
            String ciphertext = encryptAES(secretKey, input);
            String out = decryptAES(secretKey, ciphertext);
            System.out.println("对称加密: " + out + ": " + input.equals(out));
            
            // 2. 测试带有IV的对称加密
            secretKey = getRandomAES128SecretKey();
            byte[] iv = getRandomAES16IV();
            ciphertext = encryptAES(secretKey, iv, input);
            out = decryptAES(secretKey, iv, ciphertext);
            System.out.println("对称加密: " + out + ": " + input.equals(out));
            
            // 3. 测试非对称加密
            String strPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAucxoUu+PVDTusktbaHqr0D7LtnfaI5FFAgxB7"
                    + "RnfkKHRJJU0O7pjNj55O+dd8ui5TTu9PoZ6+Iiu4Xmyo//GNc4ajKGGRoKJLltSONDDqJ+Dw1N3Agc19usmMQg4XHrwh"
                    + "gLdNhHeQ5O4QPMvfOzPY370app4/X5LQvC3Xv7/25MSWieIzkV5EKqcqCPX5+hZqIZqoeaWtLm9uVSJC4XBBs2LQz46Y"
                    + "CG7nmRiNUGxLD1Y497jH20VKxq53UHXS/LiTy4QjlRCwHz11TkbKkrmdnw1QkBgrv0vyhhYrgkMmYrjL+cukxb6eyhS3l"
                    + "d3ur/z56/sUoR4/MTBPFJP+ZYGEwIDAQAB";
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(strPublicKey));
            PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
            
            String strPrivateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5zGhS749UNO6yS1toeqvQPsu2d9ojkU"
                    + "UCDEHtGd+QodEklTQ7umM2Pnk7513y6LlNO70+hnr4iK7hebKj/8Y1zhqMoYZGgokuW1I40MOon4PDU3cCBzX26yYxCDh"
                    + "cevCGAt02Ed5Dk7hA8y987M9jfvRqmnj9fktC8Lde/v/bkxJaJ4jORXkQqpyoI9fn6Fmohmqh5pa0ub25VIkLhcEGzYtD"
                    + "PjpgIbueZGI1QbEsPVjj3uMfbRUrGrndQddL8uJPLhCOVELAfPXVORsqSuZ2fDVCQGCu/S/KGFiuCQyZiuMv5y6TFvp7K"
                    + "FLeV3e6v/Pnr+xShHj8xME8Uk/5lgYTAgMBAAECggEAa7x9menToK544uYTgQfw7PYcxhfFY+5up3tYFxZzrrhGQAJMoX"
                    + "243dFoFzZYIeyU1pYXbFQqpkcLsS8SSUqdMsHqXzWiWOyEg17s1Ikpi3PDwdV6IrDvt9gu8yujEu0u32Z1w06lJWZY50C"
                    + "cfsIKl9UcAVzFX6iGF7Dhg7I1xqoJvKRbs7wLpBimgX3kNt2FxiiZ9EPo1Hwx7YauP3Wd/Dzx3J1bmg3R5PiiRWy8AdhV"
                    + "ApMKPjuqqhGec8f1IW4gT7eYRzdpI/On90hnthjJK7Cysj/lJAaQtO5NLSBjWK5SE/fFvN5NquWeMnssaaX2g1RBs7s/+"
                    + "ZMSgtjcY1SpgQKBgQDr02v/O6y3l+Aza1hPZeB08Bu0slbfo2Bpf53qBm2Qn4HUsMeeFglwJlgYETK074GfwCrDo5DLlY"
                    + "sLfleZJNMXF2PYYD5EJb7pTBBbonI+L9lSMzoFIULhVrYNdnV50Xh9mgvGk1ywnsbgNc5aD3bXcgHUfGjIa3129BtygUL"
                    + "a8wKBgQDJsWMqc+uh2Y02eGkIx26REZ5uxASEt3NJ5L+pvlJlZMiUfQvqo4TYzv/AWjMcNGebDFdUP7e+IJLRG66eZPhc"
                    + "M6CdbIkGP3iYqXAbmgEpeoIT4NzIvTjTg2IQZd9ef8ql6rlt7DfyhV0P0ebhvFpzJkrG4Jjh8L2tNvWsrt6wYQKBgFcaG"
                    + "dOFqP+OmwKi7VU2HbdTUAhnrmqfn2aX+i2L/j/iikOSn8gl/4pqvzL0dzQZGll00ta7vSlUrKysF5K65TSsMPakZZsqDd"
                    + "+BdrFByMxrQ+t2fEGUzW0JZ+iFDlLWKZjKovrPRvb9ThtWBEeDWrOsqjxfTxxnh0m+U7zxPU49AoGBAIi6pEtHQln6LWz"
                    + "bu/ijmiTmGM1mPNnrs1BIrlXYG+t4ozFmhAmQyKJh0acIftWEAShu+VS3zUwqsNzpMztVn7iBl0ShK1L8/DghxUow4NDJ"
                    + "qBzpt0KuZDOfQX90UDSz1SEdOo92L4dNOYVb+nTVR0wAjXi9EWc52JvwQiPKeFSBAoGBAIgO/4X5iGPVDDk+Fl4IWEAPv"
                    + "+DMhemnVEVGmHmXvaR4d782kq+k8C23XTw8xurt0RR0MgOtmEqRhKdunrmmy98tyjMP0QDYbZ+PgOo+mdzj6LiFmtqnbd"
                    + "34C+Q84G76th2v4r0WKP1ZLYpeUxCU8AhvJoubQSm+uH393iF9eCmE";
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(strPrivateKey));
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            
            // 3.1 公钥加密、私钥解密
            ciphertext = encryptRSA(publicKey, input);
            out = decryptRSA(privateKey, ciphertext);
            System.out.println("公钥加密、私钥解密: " + out + ": " + input.equals(out));
            
            // 3.2 私钥加密、公钥解密
            ciphertext = encryptRSA(privateKey, input);
            out = decryptRSA(publicKey, ciphertext);
            System.out.println("私钥加密、公钥解密: " + out + ": " + input.equals(out));
            
            // 4. 私钥生成签名、公钥验证签名
            String sign = genSignWithSHA256RSA(privateKey, input);
            System.out.println("签名验证结果: " + verifySignWithSHA256RSA(publicKey, input, sign));
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
